package simulator;

import graph.DijkstraDistance;

import java.util.Vector;

import simulator.processes.Process;

/**
 * Trida oddedena od tridy <code>Process</code> reprezentujici nakladni vuz.
 * 
 * @author Petr Altman
 */
public class Truck extends Process {

	private TruckStatusEnum nextStatus = TruckStatusEnum.Pripraven_na_centrale, actualStatus = TruckStatusEnum.Pripraven_na_centrale;
	
	/**
	 * Vrati aktualni stav nakladaku.
	 * @return
	 */
	public TruckStatusEnum getActualStatus() {
		return actualStatus;
	}

	private int actualPathIndex = 0;
	/**
	 * Vrati index trasy, kterou nakladni vuz prave vykonava.
	 * @return Index aktualne provadene trasy.
	 */
	public int getActualPathIndex() {
		return actualPathIndex == 0 ? 0 : actualPathIndex - 1;
	}

	private Vector<TruckPath> paths = new Vector<TruckPath>();
	/**
	 * Vrati seznam tras prirazenych tomuto nakladnimu vozu.
	 * @return Seznam tras nakladniho vozu.
	 */
	public Vector<TruckPath> getPaths() {
		return paths;
	}

	/**
	 * Pripradi seznam tras tomuto nakladnimu vozu.
	 * @param paths Seznam novych tras.
	 * @param actualTime Referencni aktualni cas.
	 */
	public void setPaths(Vector<TruckPath> paths, int actualTime) {
		this.paths = paths;
		
		if(getActualPath() == null)
			updateStartTime(actualTime);
	}
	
	private TruckPath actualPath = null;
	/**
	 * Vrati instanci aktualne provadene trasy.
	 * @return Instance aktualne provadene trasy, neni-li nakladni vuz na ceste, vrati null.
	 */
	public TruckPath getActualPath() {
		return actualPath;
	}
	
	private Path backPath = null;
	/**
	 * Vrati instanci zpatecni trasy nakladniho vozu. Neni-li zpatecni cesty naplanovana, vrati null.
	 * @return Instance zpatecni trasy.
	 */
	public Path getBackPath() {
		return backPath;
	}

	private Company company;
	/**
	 * Vrati instanci firmy, ktera nakladni vuz vlastni.
	 * @return Firma, ktera nakladni vuz vlastni.
	 */
	public Company getCompany() {
		return company;
	}

	private int palletsCount = Company.PALLET_COUNT;
	/**
	 * Vrati pocet palet, ktere ma nakladni vozidlo nalozeno.
	 * @return Pocet nalozenych palet.
	 */
	public int getPalletsCount() {
		return palletsCount;
	}
	
	private int palettsDelivered = 0;
	/**
	 * Vrati pocet dorucenych palet.
	 * @return Pocet dorucenych palet.
	 */
	public int getPalettsDelivered() {
		return palettsDelivered;
	}

	private int totalLength = 0;
	/**
	 * Vrati celkovou urazenou vzdalenost.
	 * @return Urazena vzdalenost.
	 */
	public int getTotalLength() {
		return totalLength;
	}

	/**
	 * Zjisti, zda nakladni vuz ma pozadovany pocet palet.
	 * @param neededGoods Pocet pozadovanych palet.
	 * @return true - ma dostatecny pocet palet, false - nema dostatecny pocet palet.
	 */
	public boolean hasEnoughGoods(int neededGoods) {
		int goods = 0;
		for(TruckPath path : paths)
			goods += path.getOrder().getGoodsCount();
		return Company.PALLET_COUNT - goods >= neededGoods;
	}
	
	/**
	 * Zjisti, zda vozidlu muze byt preplanovana trasa.
	 * @return True - trasa muze byt preplanovana. False - trasa nemuze byt preplanovana.
	 */
	public boolean cenBeReplanned() {
//		return actualStatus != TruckStatusEnum.Pripraven_na_centrale;
		return actualStatus != TruckStatusEnum.Vraci_se_zpet && actualStatus != TruckStatusEnum.Je_zpet;
	}
	
	/**
	 * Nastavi nakladaku nasledujici trasu k vykonani.
	 */
	private void nextPath() {
		if(actualPath != null)
			totalLength += actualPath.getLength();
		
		actualPath = paths.get(actualPathIndex); //paths.remove(0)
		actualPathIndex++;
	}
	
	/**
	 * Cstor.
	 * @param company Forma, pod kterou nakladak spada.
	 * @param path Pocatecni trasa nakladaku.
	 */
	public Truck(Company company, TruckPath path) {
		this.company = company;
		paths.add(path);
		
		nextStatus = TruckStatusEnum.Pripraven_na_centrale;
		setWakeTime(company.getScheduler().getTime());
	}
	
	@Override
	protected void process() {
		
		actualStatus = nextStatus;
		
		// provedeni zmeny stavu
		switch(nextStatus) {
		case Pripraven_na_centrale:
			
			updateStartTime(0);
			
			notifyUpdated();
			
			nextStatus = TruckStatusEnum.Prebira_objednavku;
			break;
		case Prebira_objednavku:
			nextPath();
						
			notifyUpdated();
			
			nextStatus = TruckStatusEnum.Na_ceste;
			break;
		case Na_ceste:		
			setWakeTime(getActualPath().getWayTimeAbs());
			
			notifyUpdated();
			
			if(getActualPath().getWaitTime() > 0) {
				nextStatus = TruckStatusEnum.Ceka_vylozeni;
			} else {
				nextStatus = TruckStatusEnum.Vyklada;
			}
			
			break;
		case Ceka_vylozeni:	
			setWakeTime(getActualPath().getWaitTimeAbs());
			
			notifyUpdated();
			
			nextStatus = TruckStatusEnum.Vyklada;
			break;
		case Vyklada:			
			int delivered = (getActualPath().getUnloadingTime() / Company.TIME_UNLOAD_ONE);
			
			if(getActualPath().isLateUnload() && delivered < getActualPath().getOrder().getGoodsCount())
				System.out.println();
			
			palettsDelivered += delivered; 
			palletsCount -= delivered;
			
			setWakeTime(getActualPath().getUnloadingTimeAbs());
			
			notifyUpdated();
			
			if(actualPathIndex < paths.size())
			{
				nextStatus = TruckStatusEnum.Prebira_objednavku;
				
			} else {
				nextStatus = TruckStatusEnum.Vraci_se_zpet;
			}
			break;
		case Vraci_se_zpet:
			backPath = findBackPath();
			
			setWakeTime(getActualPath().getEndTime() + backPath.getWayTime());			
			
			// vynulovani aktualni cesty
			actualPath = null;
			// nastaveni index aktualni cesty na max. mozny - respektive na preteceny
			actualPathIndex = paths.size();
			
			notifyUpdated();
			
			nextStatus = TruckStatusEnum.Je_zpet;
			break;
		case Je_zpet:
			totalLength += backPath.getLength();
			
			notifyProcessed();
			break;
		}
	}
	
	/**
	 * Metoda pro nalezeni zpatecni trasy do firmy.
	 * @return Nalezena zpatecni trasa do firmy.
	 */
	private Path findBackPath() {
		DijkstraDistance<City> dijkstr = new DijkstraDistance<City>(company.getWorld());
		dijkstr.execute(getActualPath().getOrder().getOwner(), company.getCity(), false);

		return new Path(dijkstr.getShortestPathNodes());
	}
	/**
	 * Metoda pro aktualizaci startovaciho casu nakladniho vozidla na zaklade aktualniho casu.
	 * @param actualTime Referencni aktualni cas.
	 */
	public void updateStartTime(int actualTime) {
		int wt = paths.get(actualPathIndex).getStartTime();
		if(wt < actualTime)
			wt = actualTime;
		setWakeTime(wt);
	}
	
	@Override
	public String toString() {
		return "Nakladak ID: " + getId() + (getActualPath() != null ? "; Objednavka: '" + getActualPath().getOrder().getId() + "'" : "") + "; Stav: " + getActualStatus() + ";";
	}
	
}
